package mutsumimod.patches;

import com.evacipated.cardcrawl.modthespire.lib.*;
import com.evacipated.cardcrawl.modthespire.patcher.PatchingException;
import com.megacrit.cardcrawl.actions.AbstractGameAction;
import com.megacrit.cardcrawl.actions.utility.UseCardAction;
import com.megacrit.cardcrawl.cards.AbstractCard;
import com.megacrit.cardcrawl.cards.DamageInfo;
import com.megacrit.cardcrawl.characters.AbstractPlayer;
import com.megacrit.cardcrawl.core.AbstractCreature;
import com.megacrit.cardcrawl.monsters.AbstractMonster;
import com.megacrit.cardcrawl.powers.AbstractPower;
import javassist.CannotCompileException;
import javassist.CtBehavior;
import javassist.expr.ExprEditor;
import javassist.expr.MethodCall;
import mutsumimod.actions.CopyChooseOneAction;
import mutsumimod.cards.BaseCard;
import mutsumimod.cards.custom.TwoPeopleOneBody;
import mutsumimod.cards.deprecated.NotRecordCard;
import mutsumimod.powers.TwoPeopleOneBodyPower;
import mutsumimod.util.tags.CustomTags;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Set;

public class CopyPatch extends BasePatch {

    @SpirePatch(
            clz = AbstractCard.class,
            method = SpirePatch.CLASS
    )
    public static class AbstractCardFieldPatch {
        public static SpireField<Boolean> shouldCopyUse = new SpireField<>(() -> false);
        public static SpireField<Boolean> inCopyUse = new SpireField<>(() -> false);
        public static SpireField<Boolean> inChoosingTarget = new SpireField<>(() -> false);
        public static SpireField<AbstractCard.CardTarget> originTarget = new SpireField<>(() -> com.megacrit.cardcrawl.cards.AbstractCard.CardTarget.NONE);
        public static SpireField<ArrayList<AbstractCard>> copyList = new SpireField<>(() -> new ArrayList<>());
        public static SpireField<AbstractCard> originCard=new SpireField<>(() -> null);
    }
    
    public static boolean shouldCopyUse(AbstractCard c) {
        return AbstractCardFieldPatch.shouldCopyUse.get(c)&&!AbstractCardFieldPatch.inCopyUse.get(c)
                &&!AbstractCardFieldPatch.inChoosingTarget.get(c);
    }

    @SpirePatch(
            clz=AbstractCard.class,
            method = SpirePatch.CONSTRUCTOR,
            paramtypez = {
                String.class, String.class, String.class, int.class, String.class, AbstractCard.CardType.class, AbstractCard.CardColor.class, AbstractCard.CardRarity.class, AbstractCard.CardTarget.class, DamageInfo.DamageType.class
            }

    )
    public static class AbstractCardConstructorPatch {
        @SpirePostfixPatch
        public static void postfix(AbstractCard __instance, String id, String name, String imgUrl, int cost, String rawDescription, AbstractCard.CardType type, AbstractCard.CardColor color, AbstractCard.CardRarity rarity, AbstractCard.CardTarget target, DamageInfo.DamageType dType) {
            AbstractCardFieldPatch.originTarget.set(__instance, target);
            AbstractCardFieldPatch.copyList.get(__instance).add(__instance);
        }
    }

    @SpirePatch(
            clz=AbstractCard.class,
            method = "update"

    )
    public static class AbstractCardUpdatePatch {
        @SpirePostfixPatch
        public static void postfix(AbstractCard __instance) {
            if(AbstractCardFieldPatch.inCopyUse.get(__instance)) {
                __instance.target=AbstractCardFieldPatch.originTarget.get(__instance);
            }else if(AbstractCardFieldPatch.shouldCopyUse.get(__instance)) {
                __instance.target= AbstractCard.CardTarget.SELF;
            }
        }
    }

    @SpirePatch(
            clz = AbstractPlayer.class,
            method = "useCard",
            paramtypez = {
                    AbstractCard.class, AbstractMonster.class, int.class
            }
    )
    public static class CopyUsePatch {
        @SpireInstrumentPatch
        public static ExprEditor Instrument() {
            return new ExprEditor() {
                @Override
                public void edit(MethodCall m) throws CannotCompileException {
                    if (m.getClassName().equals(AbstractCard.class.getName())
                            && m.getMethodName().equals("use")) {
                        m.replace("{ "
                                + CopyUsePatch.class.getName()
                                + ".copyUse($0,this,monster); }");
                    }
                }
            };
        }

        public static void copyUse(AbstractCard c, AbstractPlayer p, AbstractMonster m) {
            if(c instanceof BaseCard) {
                BaseCard bc = (BaseCard)c;
                bc.beforeCopy();
                if(p.hasPower(TwoPeopleOneBodyPower.POWER_ID)) {
                    if(bc.standTag>=0&&bc.standTag<=10){
                        bc.setShouldCopyUse(true);
                        bc.addCopyCards(TwoPeopleOneBody.getTenSubXCard(1,10-bc.standTag,c));
                    }
                }
            }

            if(shouldCopyUse(c)) {
                addToTop(new CopyChooseOneAction(c));
            }else{
                c.use(p,m);
                //AbstractCardFieldPatch.inCopyUse.set(c,false);
            }

            if(c instanceof BaseCard){
                ((BaseCard) c).setShouldCopyUse(((BaseCard) c).originShouldCopyUse);
            }
        }
    }

    @SpirePatch(
            clz = AbstractPlayer.class,
            method = "useCard",
            paramtypez = {
                    AbstractCard.class, AbstractMonster.class, int.class
            }
    )
    public static class AfterCopyUseHookPatch {
        @SpirePostfixPatch
        public static void postfix(AbstractPlayer __instance,AbstractCard c, AbstractMonster monster, int energyOnUse) {
            if(shouldCopyUse(c)) {
                if(c instanceof BaseCard) {
                    ((BaseCard) c).afterCopyUse();
                }
            }
        }
    }

    @SpirePatch(
            clz = UseCardAction.class,
            method = SpirePatch.CONSTRUCTOR,
            paramtypez = {
                    AbstractCard.class, AbstractCreature.class
            }
    )
    public static class UseCardActionConstructPatch {
        
        @SpireInsertPatch(
            locator = Locator.class      
        )
        public static SpireReturn insert(UseCardAction __instance, AbstractCard card, AbstractCreature target) {
            if(shouldCopyUse(card)) {
                if (__instance.exhaustCard) {
                    __instance.actionType = AbstractGameAction.ActionType.EXHAUST;
                } else {
                    __instance.actionType = AbstractGameAction.ActionType.USE;
                }
                return SpireReturn.Return();
            }
            return SpireReturn.Continue();
        }
        

        public static class Locator extends SpireInsertLocator {
            public int[] Locate(CtBehavior ctMethodToPatch) throws CannotCompileException, PatchingException {
                Matcher finalMatcher = new Matcher.FieldAccessMatcher(UseCardAction.class, "duration");

                int[] lines = LineFinder.findInOrder(ctMethodToPatch, new ArrayList<Matcher>(), finalMatcher);
                return new int[]{lines[0]+1};
            }
        }
    }

    @SpirePatch(
            clz = UseCardAction.class,
            method = "update"
    )
    public static class UseCardActionUpdatePatch {
        @SpireInstrumentPatch
        public static ExprEditor Instrument() {
            return new ExprEditor() {
                @Override
                public void edit(MethodCall m) throws CannotCompileException {
                    if (m.getMethodName().equals("onAfterUseCard")) {
                        m.replace("{ "
                                + UseCardActionUpdatePatch.class.getName()
                                + ".onAfterUseCard($0,this.targetCard, this); }");
                    }
                }
            };
        }

        public static void onAfterUseCard(AbstractPower p,AbstractCard targetCard,UseCardAction action) {
            if(shouldCopyUse(targetCard)) {
                return;
            }
            p.onAfterUseCard(targetCard,action);
        }
    }

}
